#include "MemoryManager.h"

namespace Proton 
{
	namespace Managers
	{
		MemoryManager::MemoryManager(U32 _heapSizeMB, U32 _memoryLimitMB)
		{
			pHead = nullptr;
			memoryLimitMB = memoryLimitMB;
			newHeap(_heapSizeMB);

			// TEMPORARY: create another heap for test purposes
			newHeap(10);
		}

		MemoryManager::~MemoryManager(void)
		{
			// delete heaps
			Heap* node = pHead;
			Heap* next;

			while (node != nullptr)
			{
				next = node->pNext;
				delete node;
				node = next;
			}

			pHead = nullptr;
		}

		void MemoryManager::newHeap(U32 _heapSizeInMB)
		{
			/*	check for memory limit,
				TO DO: throw exception/warning if memory limit is reached	

				if ((memoryLimitMB - (getTotalMemory()/1048576)) >= _heapSizeInMB)
					log("Memory limit reached!");	*/

			Heap* heap = new Heap(_heapSizeInMB);

			if(heap != nullptr && heap->getFreeMemory() > 0)
			{			
				// add heap to heap list
				if (pHead == nullptr)
				{
					// first heap created
					pHead = heap;
				}
				else
				{
					heap->pNext = pHead;
					pHead = heap;
				}
				
			}
			else
			{
				delete heap;
			}			
		}
		
		void* MemoryManager::allocate(U32 sizeInBytes)
		{
			void* address = pHead->allocate(sizeInBytes);
			if (address != nullptr)
				return address;

			Heap* node = pHead;			
			// iterate through link list of heaps to find enough space for allocation
			while (node != nullptr)
			{
				// If successful, return. If unsuccessful, try another heap.
				address = node->allocate(sizeInBytes);
				if (address != nullptr)
					return address;
				else
					node = node->pNext;
			}

			//	FOR NOW:
			/*	In case there is not enough space in any of the heaps or allocations were unsuccessful,
				attempt to create new heap of average size of created heaps + size wanted to be allocated	*/
			//	TO DO:
			/*	First try to defragment	*/

			// iterate linked list of heaps to find number of heaps and sum of their sizes
			U32 count = 0;
			U32 sum = 0;

			node = pHead;
			while (node != nullptr)
			{
				count++;
				sum += node->getTotalMemory();
				node = node->pNext;
			}

			/*	Create heap of average size of heaps 
				+ size wanted to be allocated. If successful, allocate from this heap
				else return nullptr.	*/

			newHeap(((sum / count) + sizeInBytes)/1048576);
			return pHead->allocate(sizeInBytes);
		}

		void MemoryManager::deallocate(void* pointer)
		{
			// search 
		}

		U32 MemoryManager::getFreeMemory()
		{
			Heap* node = pHead;
			U32 free = 0;
			while (node != nullptr)
			{
				free += node->getFreeMemory();
				node = node->pNext;
			}
			return free;
		}

		U32 MemoryManager::getTotalMemory()
		{
			Heap* node = pHead;
			U32 total = 0;
			while (node != nullptr)
			{
				total += node->getTotalMemory();
				node = node->pNext;
			}
			return total;
		}

		U32 MemoryManager::getUsedMemory()
		{
			Heap* node = pHead;
			U32 used = 0;
			while (node != nullptr)
			{
				used += node->getUsedMemory();
				node = node->pNext;
			}
			return used;
		}
	}
}